#! /usr/bin/env python

#
# CS 143 Project Bob
# Alex Fandrianto
# Input.py parses input files for the Network Simulator.
# It starts all processes and informs a Metrics of what it needs to track
#

import sys, string
from SimPy.Simulation import *
from Router import Router
from Link import Link
from Host import Host
from NIC import NIC
from Metrics import Metrics

# Define default simulation parameters. Replaced with command-line arguments if available.
ROUTER_UPDATE_INTERVAL = 5
HOST_TIMEOUT = 1
ALPHA = 50
LINK_QUALITY = 1#.995
SIMULATION_TIME = 100

PROCESSING_TIME = 0 # NOT USED

# The Controller process starts flows as necessary during the simulation.
# Uses timesteps of .01, though this can be modified (not necessary for current testcases)
class Controller(Process):
    def __init__(self, flows):
        Process.__init__(self)
        self.flows = flows # [(senderIndex, receiverIndex, sizeOfFlowInBytes, startTime, FlowProtocol)]
        self.reminders = {} # of time, host to remind them of Timeouts
        
    def go(self):
        while True:
            t = now()
            for f in self.flows:
                if f[3] <= t:
                    devices[f[0]].send("-"*f[2], PACKET_SIZE, hostIPs[f[1]], f[4])
                    self.flows.remove(f)
                    # f[0] = senderHost
                    # f[1] = receiverHost
                    # f[2] = dataSize
                    # f[3] = timeToSend
                    # f[4] = CongestionControlType
            times = []
                    
            for time in self.reminders:
                if time <= now():
                    host = self.reminders[time]
                    times.append(time)
                    if host.passive():
                        print "Reminding the host at %f" % now()
                        reactivate(host)
            for time in times:
                del self.reminders[time] # Remove unneeded times
                        
            yield hold, self, .01
            
    def remindMe(self, host, time): # Hosts call this to add a reminder
        self.reminders[time] = host
            
# Parse the input of the file to build our network
def parseInput(filenameIn):
    f = open(filenameIn, "r")
    
    mode = '' # Devices, Network, Flows, LinkInfo, or FlowInfo
    
    countHosts = 0
    countRouters = 0
    countLinks = 0
    
    for line in f:
        line = line.strip()
        newMode = findMode(line, mode) # Replace the old mode if necessary
        if newMode != mode:
            mode = newMode
            continue
        if mode == '': # haven't found a mode yet, keep going
            continue
        tokens = line.split(' ')
        
        if mode == 'Devices':
            if len(tokens) == 1: # only 1 token allowed
                if tokens[0] == 'Host':
                    # make a new host
                    host = Host('')
                    devices.append(host)
                    countHosts += 1
                    print 'Make a new host! %d' % countHosts

                elif tokens[0] == 'Router':
                    # make a new router
                    devices.append(Router(ROUTER_UPDATE_INTERVAL, []))
                    countRouters += 1
                    print 'Make a new router! %d' % countRouters
                    
        elif mode == 'Network':
            if len(tokens) == 7: # it better be 7 to define a link
                # make the link for the device pair
                
                a = int(tokens[0])
                b = int(tokens[1])
                rate = int(tokens[2])
                prop_delay = float(tokens[3])
                buf_size = int(int(tokens[4]) / PACKET_SIZE)
                ip1String = tokens[5]
                ip2String = tokens[6]
                print "My buffer size is: ", buf_size
                
                countLinks += 1
                
                ip1Split = ip1String.split('.')
                ip2Split = ip2String.split('.')
                ip1 = (int(ip1Split[0]), int(ip1Split[1]), int(ip1Split[2]), int(ip1Split[3]))
                ip2 = (int(ip2Split[0]), int(ip2Split[1]), int(ip2Split[2]), int(ip2Split[3]))

                # And then make the link bridging the NICs
                link = Link(rate, LINK_QUALITY, prop_delay)
                
                gateway1 = None
                gateway2 = None
                if not isinstance(devices[a], Router): # then devices[b] is a router
                    gateway1 = ip2
                if not isinstance(devices[b], Router): # then devices[a] is a router
                    gateway2 = ip1
                
                # add NICs to their devices...
                # parent device, connected link, NIC's IP, subnet, maxBufSendSize, maxBufRcvSize, receiveDelay = 0
                nic1 = NIC(devices[a], link, ip1, (255, 255, 255, 0), gateway1, buf_size, buf_size, PROCESSING_TIME)
                nic2 = NIC(devices[b], link, ip2, (255, 255, 255, 0), gateway2, buf_size, buf_size, PROCESSING_TIME)
                devices[a].addNIC(nic1)
                devices[b].addNIC(nic2)
                
                NICS.append(nic1)
                NICS.append(nic2)
                
                hostIPs[a] = ip1
                hostIPs[b] = ip2
                
                link.connect(nic1)
                link.connect(nic2)
    
                links.append(link)
                print 'Hey, a new link! From %d to %d' % (a, b)
                
        elif mode == 'Flows':
            if len(tokens) == 5: # it better be 5 to define a flow
                # make the flow already
                sender = int(tokens[0])
                receiver = int(tokens[1])
                size = int(tokens[2])
                start_time = float(tokens[3])
                ccType = tokens[4]
                
                flow = (sender, receiver, size, start_time, ccType)
                flows.append(flow)
                
                print 'Hey, a new flow! From %d to %d' % (sender, receiver)
                
        elif mode == 'LinkInfo':
            if len(tokens) == 1: # it better be only 1 value
                value = int(tokens[0]) # this is your link
                
                mLinks.append((links[value], NICS[2 * value], NICS[2 * value + 1]))
                mNICs.append(NICS[2 * value])
                mNICs.append(NICS[2 * value + 1])
                print 'This link is important: %d' % value
                
        elif mode == 'FlowInfo':
            if len(tokens) == 2: # you need two values
                sender = int(tokens[0])
                receiver = int(tokens[1])
                
                mFlows.append((devices[sender], devices[receiver])) 
                
                print 'I wanna measure this: %d to %d' % (sender, receiver)
        
    f.close()

# Helper function for parseFile that determines what the input mode the parser should be expecting
def findMode(line, oldMode):
    if line == 'Devices' or line == 'Network' or line == 'Flows' or line == 'LinkInfo' or line == 'FlowInfo':
        if line == 'Network':
            global hostIPs
            hostIPs = [(0, 0, 0, 0)] * len(devices) # initialize the list again whenever we enter Network mode
            pass
        return line
    return oldMode

# After parsing, runSimulation() will begin all processes and simulate for a while.
# Then metric must display its information. (The metric variable is global.)
def runSimulation():
    initialize()
    metric = Metrics(mLinks, mFlows)
    activate(metric, metric.go()) # Metrics isn't really a process though
    
    # Inform the controller of its flows
    controller = Controller(flows)
    
    # Activate the devices, and assign the controller if its a Host
    for d in devices:
        d.metrics = metric
        activate(d, d.go())
        if isinstance(d, Host):
            d.controller = controller
            d.setTimeout(HOST_TIMEOUT)
            d.setALPHA(ALPHA)
            
    # Activate the links
    for l in links:
        l.metrics = metric
        activate(l, l.go())
        
    # Activate the NICs
    for n in NICS:
        n.metrics = metric
        activate(n, n.go())
        
    # The controller is ready.
    activate(controller, controller.go())
    print 'Starting Simulation'
    simulate(until = SIMULATION_TIME)
    print 'Done with simulation'
    metric.display()
    
    print 'Done with program'
    
#
# Main body of program.
#

usage = "usage: python Input.py [filenameIn] [+RoutingUpdateInterval] [+HostTimeout] [+Alpha] [+LinkQuality] [+SimulationTime]"

# Parse input arguments
if len(sys.argv) < 2 or len(sys.argv) > 7:
    print usage
    raise SystemExit
if len(sys.argv) >= 3:
    ROUTER_UPDATE_INTERVAL = float(sys.argv[2])

if len(sys.argv) >= 4:
    HOST_TIMEOUT = float(sys.argv[3])

if len(sys.argv) >= 5:
    ALPHA = float(sys.argv[4])

if len(sys.argv) >= 6:
    LINK_QUALITY = float(sys.argv[5])

if len(sys.argv) == 7:
    SIMULATION_TIME = float(sys.argv[6])

PACKET_SIZE = 1000 # 1000 B
metric = None
devices = []
hostIPs = [] # nonsense if router
links = []
NICS = []
flows = []  # list of everything I need to send in the future
mLinks = [] # list of links we need to measure. Maybe we should also give him the NICs too... :(
mNICs = [] # list of NICs that surround the links
mFlows = [] # list of (deviceSender, deviceReceiver) tuples we need to measure
    
parseInput(sys.argv[1])
runSimulation()